package com.yiwyn.bytebuddy.proxy;

import com.yiwyn.bytebuddy.proxy.advice.SayHelloAdvice;
import com.yiwyn.bytebuddy.service.HelloService;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatchers;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;


/**
 * bean实例化之前
 */
@Slf4j
@Component
public class ByteBuddyHelloServiceBeanFactoryPostProcessor implements BeanFactoryPostProcessor, BeanClassLoaderAware {

    private ClassLoader classLoader;


    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        String[] beanDefinitionNames = beanFactory.getBeanNamesForType(HelloService.class);
        for (String beanDefinitionName : beanDefinitionNames) {
            // 判断指定名称的 Bean 是否与某个类型匹配， 与getBean()相比不会创建bean
            boolean typeMatch = beanFactory.isTypeMatch(beanDefinitionName, HelloService.class);

            if (typeMatch) {
                // 获取beanDefinition
                BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
                AbstractBeanDefinition abstractBeanDefinition = (AbstractBeanDefinition) beanDefinition;

                // 从classloader中根据className获取Class<？>
                Class<?> beanClass = ClassUtils.resolveClassName(abstractBeanDefinition.getBeanClassName(), classLoader);

                Class<?> sayHello = new ByteBuddy()
                        .subclass(beanClass)
                        .method(ElementMatchers.named("sayHello"))
                        .intercept(Advice.to(SayHelloAdvice.class))
                        .make()
                        .load(classLoader)
                        .getLoaded();
                abstractBeanDefinition.setBeanClass(sayHello);
            }

        }
    }


    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

}
